import json
import logging
import threading
import time
import uuid
from copy import deepcopy
from string import ascii_letters, digits

from django.utils.deprecation import MiddlewareMixin
from rest_framework.renderers import JSONRenderer

from utils.mixins.common_mixins.logger import common_logger, operation_auto_logger

local = threading.local()

ALLOW_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
DENY_PATH_LIST = ['swagger']
SECRET_KEY = ['password', 'token', 'access', 'refresh']
BUFFER = ascii_letters + digits


def get_local_request():
    request_method = getattr(local, 'request_method', str())
    request_path = getattr(local, 'request_path', str())
    request_ip = getattr(local, 'request_ip', str())
    request_data = getattr(local, 'request_data', str())
    username = getattr(local, 'username', str())
    cost_time = getattr(local, 'cost_time', str())
    response_data = getattr(local, 'response_data', str())
    status_code = getattr(local, 'status_code', str())
    request_id = getattr(local, 'request_id', str())

    return request_method, request_path, request_ip, request_data, username, cost_time, response_data, status_code, \
        request_id


def is_secret_key(key):
    for secret_key in SECRET_KEY:
        if secret_key in key:
            return True
    return False


def hide_secret(data):
    if isinstance(data, dict):
        for k, v in data.items():
            if is_secret_key(k) and isinstance(v, str):
                data[k] = '******'
            elif isinstance(v, (dict, list)):
                data[k] = hide_secret(v)
    elif isinstance(data, list):
        for i, value in enumerate(data):
            data[i] = hide_secret(value)
    return data


def get_request_ip(request):
    """
    获取请求IP
    """
    if x_forwarded_for := request.META.get('HTTP_X_FORWARDED_FOR', ''):
        # HTTP_X_FORWARDED_FOR 最后一个才是真的client ip
        ip = x_forwarded_for.split(',')[-1].strip()
        return ip
    ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None)
    return ip or 'unknown'


def get_request_data(request):
    """
    获取请求参数
    """
    if request_data := getattr(request, 'request_data', None):
        return hide_secret(request_data)
    data = {**request.GET.dict(), **request.POST.dict()}
    if not data:
        try:
            body = request.body
            if body:
                data = json.loads(body)
        except Exception as e:
            common_logger.warning('获取body中参数失败, error: %s' % e)
    return json.dumps(hide_secret(data), ensure_ascii=False)


def truncate_list(data):
    """
    截取列表
    """
    if isinstance(data, list):
        if len(data) > 0:
            data[0] = truncate_list(data[0])
        if len(data) > 1:
            data = data[:2]
            data[1] = '...'
            return data
        return data
    elif isinstance(data, dict):
        for k, v in data.items():
            data[k] = truncate_list(v)
    else:
        return data
    return data


def get_response_data(response):
    """
    获取响应数据
    """
    data = getattr(response, 'data', None)
    try:
        # 必须深拷贝否则不生效
        _data_copy = deepcopy(data)
        if hasattr(response, 'headers'):
            content_type = response.headers.get('Content-Type', str())
            if 'text/html' in content_type:
                resp_data = _data_copy
                return resp_data
        # 使用drf自带json render进行序列化
        renderer = JSONRenderer()
        ret = json.loads(renderer.render(_data_copy))
        ret = truncate_list(ret)
        resp_data = json.dumps(hide_secret(ret), ensure_ascii=False)
        # 截取前1000个字符
        if len(resp_data) > 3000:
            resp_data = resp_data[:2997] + '...'
        return resp_data
    except Exception as e:
        common_logger.warning(f'返回数据无法被序列化, error: {e}, data: {data}')


def get_request_path(request, *args):
    """
    获取请求路径
    """
    if request_path := getattr(request, 'request_path', None):
        return request_path
    values = []
    for arg in args:
        if len(arg) == 0:
            continue
        if isinstance(arg, str):
            values.append(arg)
        elif isinstance(arg, (tuple, set, list)):
            values.extend(arg)
        elif isinstance(arg, dict):
            values.extend(arg.values())
    if len(values) == 0:
        return request.path
    path = request.path
    for value in values:
        path = path.replace('/' + value, '/' + '{id}')
    return path


def get_short_id():
    _id = uuid.uuid4().hex
    short_id = str()
    for i in range(0, 8):
        start = i * 4
        end = i * 4 + 4
        val = int(_id[start:end], 16)
        short_id += BUFFER[val % 62]
    return short_id


def enable_log(request_method, request_path):
    if request_method not in ALLOW_METHODS:
        return False
    try:
        if request_path.split('/')[1] in DENY_PATH_LIST:
            return False
    except Exception as e:
        common_logger.warning(f'拆分request_path失败, error: {e}, request_path: {request_path}')
        return False
    return True


class RequestFilter(logging.Filter):

    def filter(self, record):
        request_method, request_path, request_ip, request_data, username, cost_time, response_data, status_code, \
            request_id = get_local_request()
        # if request_method in ['HEAD', '']:
        #     return False
        record.request_method = request_method
        record.request_path = request_path
        record.request_ip = request_ip
        record.request_data = request_data
        record.username = username
        record.cost_time = cost_time
        record.response_data = response_data
        record.status_code = status_code
        record.request_id = request_id
        return True


class DbLogFilter(RequestFilter):

    def filter(self, record):
        record.risk_level = getattr(local, 'risk_level', 1)
        return super().filter(record)


class LogRequestCollectMiddleware(MiddlewareMixin):
    """
    日志收集中间件
    """

    @staticmethod
    def _process_request(request):
        request_method = request.method
        request_path = get_request_path(request)
        local.request_method = request_method
        local.request_path = request_path
        local.request_id = get_short_id()
        local.request_ip = get_request_ip(request)
        local.request_data = get_request_data(request)
        local.start_time = time.time()

    def process_request(self, request):
        self._process_request(request)


class LogResponseCollectMiddleware(MiddlewareMixin):
    """
    日志收集中间件
    """

    @staticmethod
    def _process_response(request, response):
        local.response_data = get_response_data(response)
        local.status_code = getattr(response, 'status_code', None)
        if getattr(request.user, 'username', None):
            local.username = request.user.username
        else:
            local.username = '未登录用户'

    def process_response(self, request, response):
        self._process_response(request, response)
        return response


class AutoLogMiddleware(MiddlewareMixin):

    def __init__(self, get_response=None):
        super().__init__(get_response)

    @staticmethod
    def __handler_request(request):
        request_method = getattr(local, 'request_method', request.method)
        request_path = getattr(local, 'request_path', get_request_path(request))
        request_data = getattr(local, 'request_data', get_request_data(request))
        if enable_log(request_method, request_path):
            common_logger.info(f'Request_params: {request_data}')

    @staticmethod
    def __handler_response(request, response):
        request_method = getattr(local, 'request_method', request.method)
        request_path = getattr(local, 'request_path', get_request_path(request))
        status_code = getattr(local, 'status_code', response.status_code)
        response_data = getattr(local, 'response_data', get_response_data(response))
        cost_time = round((time.time() - getattr(local, 'start_time', time.time())) * 1000, 2)
        if enable_log(request_method, request_path):
            common_logger.info(f'Response: {status_code=}, {response_data=}, {cost_time=}ms')
        return response

    def process_request(self, request):
        # 产生request对象, 进行url匹配之前调用
        self.__handler_request(request)

    def process_response(self, request, response):
        # 视图函数调用之后, 内容返回给浏览器之前调用
        return self.__handler_response(request, response)


# 审计_操作日志
class AuditLogMiddleware(MiddlewareMixin):

    @staticmethod
    def __handler_response(request, response):
        # GET请求不记录
        if request.method == 'GET':
            return response

        local.cost_time = round((time.time() - getattr(local, 'start_time', time.time())) * 1000, 2)
        # 将日志写入到数据库, risk_level大于0, msg不为 token_not_valid
        risk_level = getattr(local, 'risk_level', 0)
        if risk_level > 0 and getattr(response, 'data', None) and getattr(response.data, 'msg',
                                                                          None) != 'token_not_valid':
            if local.status_code < 400:
                operation_auto_logger.info('autolog')
            else:
                operation_auto_logger.error('autolog')
        elif local.status_code >= 400:
            operation_auto_logger.error('autolog')
        return response

    def process_response(self, request, response):
        return self.__handler_response(request, response)
